package org.dromara.utils;

import com.aspose.words.*;
import org.dromara.genealogy.domain.TocItem;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;

/**
 * @author YWQ
 * @date 2025/8/20 17:30
 * @description WordDocumentParser类用于解析Word文档文件，该类提供了读取和处理Microsoft Word文档内容的功能，支持解析文档中的文本、表格、图片等元素。
 */
public class WordDocumentParser {

    public static void main(String[] args) throws Exception {
        AsposeUtils.registerWord();
        Document document = new Document("D:\\project\\zszhsystem\\word\\标书制作二期开发文档\\标书模板\\昆明产业开发投资有限责任公司持有A公司的审计服务机构选聘项目（发售稿）.docx");
        List<TocItem> tocItems = extractTocFromWord(document);
        extractContentFromWord(document, tocItems, "D:\\home\\admin\\www\\tomcat\\zszhsystem\\upload\\20250806", "/file");
        System.out.println(tocItems);
    }

    /**
     * 解析Word文档并提取目录结构及其对应的内容。
     *
     * @param inputStream Word文档输入流
     * @param imagesFolder      图片保存的文件夹路径
     * @param imagesFolderAlias 图片文件夹的别名路径
     * @return 包含目录项的列表，每个目录项包含标题、页码、级别和内容
     * @throws Exception 如果在加载或解析文档时发生错误
     */
    public static List<TocItem> parseWordDocument(InputStream inputStream, String imagesFolder, String imagesFolderAlias) throws Exception {
        // 加载Word文档
        Document doc = new Document(inputStream);

        // 提取目录结构
        List<TocItem> tocItems = extractTocFromWord(doc);

        // 提取目录项对应内容
        extractContentFromWord(doc, tocItems, imagesFolder, imagesFolderAlias);

        return tocItems;
    }

    /**
     * 从Word文档中提取目录结构。
     *
     * @param doc Word文档对象
     * @return 目录项列表
     */
    private static List<TocItem> extractTocFromWord(Document doc) {
        List<TocItem> tocItems = new ArrayList<>();
        Deque<TocItem> stack = new ArrayDeque<>();

        // 方法1: 通过样式提取标题
        // 获取文档中所有的段落节点
        NodeCollection paragraphs = doc.getChildNodes(NodeType.PARAGRAPH, true);
        for (int i = 0; i < paragraphs.getCount(); i++) {
            Paragraph paragraph = (Paragraph) paragraphs.get(i);
            String styleName = paragraph.getParagraphFormat().getStyle().getName();

            // 判断段落是否为标题样式（以"Heading"开头的样式）
            if (styleName != null && styleName.startsWith("Heading")) {
                int level = extractHeadingLevel(styleName);
                String title = paragraph.getText().trim();

                // 只处理非空标题
                if (!title.isEmpty()) {
                    // 估算标题所在的页码
                    int pageNumber = estimatePageNumber(doc, paragraph);
                    TocItem tocItem = new TocItem(title, pageNumber, level);

                    // 处理标题的层级关系，维护栈结构来表示目录树
                    // 当栈顶元素的层级大于等于当前标题层级时，弹出栈顶元素
                    while (!stack.isEmpty() && stack.peek().getLevel() >= level) {
                        stack.pop();
                    }

                    // 将当前标题项添加到目录结构中
                    if (stack.isEmpty()) {
                        // 如果栈为空，说明是顶级标题，直接添加到根目录列表
                        tocItems.add(tocItem);
                    } else {
                        // 否则作为子标题添加到当前父级标题下
                        /*stack.peek().getChildren().add(tocItem);*/ //暂时不处理子集，只处理1级标题，因为一级包含所有后代的内容
                    }

                    // 将当前标题项压入栈中，用于后续层级关系处理
                    stack.push(tocItem);
                }
            }
        }

        // 方法2: 如果目录项为空，则从文档字段中提取目录信息
        if (tocItems.isEmpty()) {
            // 获取文档中的所有字段
            FieldCollection fields = doc.getRange().getFields();
            // 遍历所有字段，查找目录字段
            for (int i = 0; i < fields.getCount(); i++) {
                Field field = fields.get(i);
                // 判断字段类型是否为目录字段
                if (field.getType() == FieldType.FIELD_TOC) {
                    // 获取目录字段的父节点
                    Node tocNode = field.getStart().getParentNode();
                    if (tocNode != null) {
                        // 从目录字段中提取目录项
                        extractTocFromField(tocNode, tocItems);
                    }
                }
            }
        }

        return tocItems;
    }

    /**
     * 从样式名称中提取标题级别。
     *
     * @param styleName 样式名称（如 "Heading 1"）
     * @return 标题级别（如 1），如果无法解析则默认返回 1
     */
    private static int extractHeadingLevel(String styleName) {
        try {
            // 从样式名中提取级别（如 "Heading 1" -> 1）
            String levelStr = styleName.replaceAll("[^0-9]", "");
            return levelStr.isEmpty() ? 1 : Integer.parseInt(levelStr);
        } catch (NumberFormatException e) {
            return 1; // 默认级别
        }
    }

    /**
     * 估算段落在文档中的页码。
     *
     * @param doc       Word文档对象
     * @param paragraph 要估算页码的段落
     * @return 估算的页码（从1开始计数）
     */
    private static int estimatePageNumber(Document doc, Paragraph paragraph) {
        // 使用Aspose的布局收集器获取精确的页码
        LayoutCollector layoutCollector = new LayoutCollector(doc);
        int pageNumber;
        try {
            pageNumber = layoutCollector.getStartPageIndex(paragraph) + 1;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return Math.max(1, pageNumber);
    }

    /**
     * 从目录字段中提取目录项。
     *
     * @param tocNode  目录字段节点
     * @param tocItems 存储提取出的目录项的列表
     */
    private static void extractTocFromField(Node tocNode, List<TocItem> tocItems) {
        // 递归处理目录字段中的内容
        NodeCollection childNodes = tocNode.getDocument().getChildNodes(NodeType.ANY, true);
        for (int i = 0; i < childNodes.getCount(); i++) {
            Node node = childNodes.get(i);
            if (node.getNodeType() == NodeType.PARAGRAPH) {
                Paragraph para = (Paragraph) node;
                String text = para.getText();

                // 尝试解析目录项文本（格式通常为 "标题........页码"）
                String[] parts = text.split("\\.+\\s*");
                if (parts.length >= 2) {
                    try {
                        String title = parts[0].trim();
                        int pageNumber = Integer.parseInt(parts[parts.length - 1].trim());

                        // 根据缩进估算级别
                        int level = estimateLevelFromIndentation(para);

                        tocItems.add(new TocItem(title, pageNumber, level));
                    } catch (NumberFormatException e) {
                        // 页码解析失败，跳过此项
                    }
                }
            }
        }
    }

    /**
     * 根据段落的左缩进估算目录级别。
     *
     * @param para 段落对象
     * @return 估算的目录级别（1~5）
     */
    private static int estimateLevelFromIndentation(Paragraph para) {
        // 根据段落左缩进估算目录级别
        double leftIndent = para.getParagraphFormat().getLeftIndent();
        if (leftIndent < 10) {
            return 1;
        }
        if (leftIndent < 20) {
            return 2;
        }
        if (leftIndent < 30) {
            return 3;
        }
        if (leftIndent < 40) {
            return 4;
        }
        return 5;
    }

    /**
     * 从Word文档中提取与目录项对应的内容，并转换为HTML格式。
     *
     * @param doc      Word文档对象
     * @param tocItems 目录项列表
     * @param imagesFolder      图片保存的文件夹路径
     * @param imagesFolderAlias 图片文件夹的别名路径
     * @throws Exception 如果在提取或转换过程中发生错误
     */
    private static void extractContentFromWord(Document doc, List<TocItem> tocItems, String imagesFolder, String imagesFolderAlias) throws Exception {
        // 获取文档布局信息
        doc.updatePageLayout();

        // 创建HTML保存选项
        HtmlSaveOptions saveOptions = new HtmlSaveOptions();
        saveOptions.setExportImagesAsBase64(false); // 图片转为 Base64 嵌入
        saveOptions.setExportFontsAsBase64(true);  // 字体转为 Base64 嵌入
        saveOptions.setExportTextInputFormFieldAsText(true); // 表单字段转为文本
        saveOptions.setImagesFolder(imagesFolder); // 图片保存的文件夹路径
        saveOptions.setImagesFolderAlias(imagesFolderAlias); // 图片文件夹的别名路径
        saveOptions.setExportRoundtripInformation(false);
        saveOptions.setExportPageMargins(true);
        // 保留更多原始格式
        saveOptions.setPrettyFormat(true);
        saveOptions.setExportHeadersFootersMode(ExportHeadersFootersMode.NONE); // 不导出页眉页脚

        // 为每个目录项提取内容（包括递归处理子项）
        for (TocItem item : tocItems) {
            extractContentForTocItem(doc, item, saveOptions);
        }
    }

    /**
     * 为单个目录项及其子项提取内容
     *
     * @param doc Word文档对象
     * @param tocItem 目录项
     * @param saveOptions HTML保存选项
     * @throws Exception 如果在提取或转换过程中发生错误
     */
    private static void extractContentForTocItem(Document doc, TocItem tocItem, HtmlSaveOptions saveOptions) throws Exception {
        // 为当前目录项提取内容
        extractSingleTocItemContent(doc, tocItem, saveOptions);

        // 递归处理子项
        if (tocItem.getChildren() != null && !tocItem.getChildren().isEmpty()) {
            for (TocItem child : tocItem.getChildren()) {
                extractContentForTocItem(doc, child, saveOptions);
            }
        }
    }

    /**
     * 为单个目录项提取内容
     *
     * @param doc Word文档对象
     * @param tocItem 目录项
     * @param saveOptions HTML保存选项
     * @throws Exception 如果在提取或转换过程中发生错误
     */
    private static void extractSingleTocItemContent(Document doc, TocItem tocItem, HtmlSaveOptions saveOptions) throws Exception {
        // 查找标题段落
        NodeCollection paragraphs = doc.getChildNodes(NodeType.PARAGRAPH, true);
        Paragraph startParagraph = null;

        // 精确匹配标题段落
        for (int i = 0; i < paragraphs.getCount(); i++) {
            Paragraph paragraph = (Paragraph) paragraphs.get(i);
            String paragraphText = paragraph.getText().trim();

            // 更严格的标题匹配（去除空白字符后比较）
            if (paragraphText.equals(tocItem.getTitle().trim())) {
                startParagraph = paragraph;
                break;
            }
        }

        if (startParagraph == null) {
            // 如果找不到精确匹配的标题，尝试模糊匹配
            for (int i = 0; i < paragraphs.getCount(); i++) {
                Paragraph paragraph = (Paragraph) paragraphs.get(i);
                String paragraphText = paragraph.getText().trim();

                if (paragraphText.contains(tocItem.getTitle().trim()) ||
                        tocItem.getTitle().trim().contains(paragraphText)) {
                    startParagraph = paragraph;
                    break;
                }
            }
        }

        if (startParagraph != null) {
            // 创建临时文档来保存提取的内容
            Document tempDoc = new Document();
            tempDoc.removeAllChildren();
            tempDoc.ensureMinimum();

            // 首先添加标题段落本身到临时文档中
            Node importedTitleNode = tempDoc.importNode(startParagraph, true);
            tempDoc.getFirstSection().getBody().appendChild(importedTitleNode);

            // 获取文档中的所有节点（跨节）
            NodeCollection allNodes = doc.getChildNodes(NodeType.ANY, true);

            // 找到起始节点在所有节点中的位置
            int startIndex = -1;
            for (int i = 0; i < allNodes.getCount(); i++) {
                if (allNodes.get(i) == startParagraph) {
                    startIndex = i;
                    break;
                }
            }

            if (startIndex != -1) {
                // 查找内容结束位置
                Node endNode = findEndNode(allNodes, startIndex, tocItem.getLevel());

                // 收集内容节点（从标题后的第一个节点开始）
                for (int i = startIndex + 1; i < allNodes.getCount(); i++) {
                    Node currentNode = allNodes.get(i);

                    // 如果遇到结束节点，停止收集
                    if (currentNode == endNode) {
                        break;
                    }

                    // 跳过表格内的文本节点，避免重复提取
                    if (isInsideTable(currentNode)) {
                        continue;
                    }

                    // 只处理段落和表格节点，避免插入不支持的节点类型
                    if (currentNode.getNodeType() == NodeType.PARAGRAPH ||
                            currentNode.getNodeType() == NodeType.TABLE) {

                        try {
                            // 使用importNode将节点导入到临时文档中
                            Node importedNode = tempDoc.importNode(currentNode, true);
                            tempDoc.getFirstSection().getBody().appendChild(importedNode);
                        } catch (Exception e) {
                            // 如果插入失败，跳过此节点
                            System.err.println("无法插入节点类型: " + currentNode.getNodeType() + ", 跳过此节点");
                        }
                    }
                }
            }

            // 将临时文档转换为HTML
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            tempDoc.save(outputStream, saveOptions);
            String htmlContent = outputStream.toString("UTF-8");

            // 清理HTML
            htmlContent = cleanHtml(htmlContent);

            tocItem.setContent(htmlContent);
        }
    }

    /**
     * 检查节点是否在表格内
     *
     * @param node 要检查的节点
     * @return 如果节点在表格内则返回true，否则返回false
     */
    private static boolean isInsideTable(Node node) {
        Node parent = node.getParentNode();
        while (parent != null) {
            if (parent.getNodeType() == NodeType.TABLE) {
                return true;
            }
            parent = parent.getParentNode();
        }
        return false;
    }

    /**
     * 查找目录项内容的结束节点
     *
     * @param allNodes 文档中的所有节点
     * @param startIndex 起始节点索引
     * @param level 当前目录项级别
     * @return 内容结束节点
     */
    private static Node findEndNode(NodeCollection allNodes, int startIndex, int level) {
        for (int i = startIndex + 1; i < allNodes.getCount(); i++) {
            Node node = allNodes.get(i);
            if (node.getNodeType() == NodeType.PARAGRAPH) {
                Paragraph paragraph = (Paragraph) node;
                String styleName = paragraph.getParagraphFormat().getStyle().getName();

                // 如果遇到标题样式
                if (styleName != null && styleName.startsWith("Heading")) {
                    int headingLevel = extractHeadingLevel(styleName);
                    // 如果遇到同级或更高级别的标题，则停止
                    if (headingLevel <= level) {
                        return node;
                    }
                }
            }
        }
        return null; // 到达文档末尾
    }

    /**
     * 清理HTML字符串，移除不必要的文档结构标签。
     *
     * @param html 原始HTML字符串
     * @return 清理后的HTML字符串
     */
    private static String cleanHtml(String html) {
        // 移除完整的HTML文档结构
        String cleaned = html;

        // 移除<!DOCTYPE>声明
        cleaned = cleaned.replaceAll("(?s)<!DOCTYPE.*?>", "");

        // 移除<html>标签及其属性
        cleaned = cleaned.replaceAll("(?s)<html[^>]*>", "");

        // 移除<head>部分
        cleaned = cleaned.replaceAll("(?s)<head.*?</head>", "");

        // 移除<body>标签及其属性
        cleaned = cleaned.replaceAll("(?s)<body[^>]*>", "");

        // 移除</body>和</html>标签
        cleaned = cleaned.replaceAll("(?s)</body>\\s*</html>", "");

        // 移除包含&#xa0;的空段落（精确匹配）
        //cleaned = cleaned.replaceAll("(?s)<p\\s+[^>]*margin-top:0pt;[^>]*margin-bottom:0pt[^>]*>\\s*<span[^>]*>&#xa0;</span>\\s*</p>", "");

        // 移除书签的<a>标签但保留其中的内容
        cleaned = cleaned.replaceAll("(?s)<a\\s+[^>]*name\\s*=\\s*[\"'][^\"']*?[\"'][^>]*>(.*?)</a>", "$1");

        // 移除连续的&#xa0;实体（不间断空格）
        cleaned = cleaned.replaceAll("(&#xa0;)+", "");

        // 移除连续的&nbsp;实体（另一种不间断空格表示法）
        cleaned = cleaned.replaceAll("(&nbsp;)+", "");

        // 移除混合的不间断空格实体
        cleaned = cleaned.replaceAll("(&[#x]?[0-9a-fA-F]+;\\s*)+", "");

        return cleaned.trim();
    }

}
